package com.minivision.fdiot.mqtt.handler;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

import com.google.gson.Gson;
import com.minivision.fdiot.domain.*;
import com.minivision.fdiot.entity.*;
import com.minivision.fdiot.rest.param.detect.SearchsParam;
import com.minivision.fdiot.rest.result.detect.SearchOneResult;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import com.minivision.fastdfs.FdfsService;
import com.minivision.fdiot.common.CommonConstants;
import com.minivision.fdiot.common.bus.BusPublisher;
import com.minivision.fdiot.common.bus.RemoteMessage;
import com.minivision.fdiot.exception.ErrorType;
import com.minivision.fdiot.exception.FacePlatException;
import com.minivision.fdiot.mqtt.core.DeviceCmdHandler;
import com.minivision.fdiot.mqtt.core.DeviceCmdHandler.CmdHandler;
import com.minivision.fdiot.mqtt.core.MqttMessageHandler.MqttMessageParam;
import com.minivision.fdiot.mqtt.core.MqttMessageHandler.MqttMessageParam.ParamType;
import com.minivision.fdiot.mqtt.message.AddressBookMessage;
import com.minivision.fdiot.mqtt.message.BinaryStatus;
import com.minivision.fdiot.mqtt.message.SocietyMessage;
import com.minivision.fdiot.mqtt.message.Status;
import com.minivision.fdiot.mqtt.protocol.ActiveCodeUtil;
import com.minivision.fdiot.protopool.Packet.Head.CmdCode;
import com.minivision.fdiot.protopool.Packet.Head.Type;
import com.minivision.fdiot.rest.param.detect.SearchParam;
import com.minivision.fdiot.rest.result.detect.SearchResult;
import com.minivision.fdiot.rest.result.detect.SearchResult.Result;
import com.minivision.fdiot.service.DeviceService;
import com.minivision.fdiot.service.FaceCommonService;
import com.minivision.fdiot.service.FaceService;
import com.minivision.fdiot.service.FaceSetService;
import com.minivision.fdiot.service.MqttLogService;
import com.minivision.fdiot.service.StatsService;
import com.minivision.fdiot.util.FeatureUtils;
import com.minivision.fdiot.util.ServiceUtils;

import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.springframework.util.StringUtils;

/**
 * 
 * 通用的系统级消息处理类
 *
 */
@Service
@DeviceCmdHandler
@Slf4j
public class CommonHandler {

  @Autowired
  private DeviceService deviceService;
  
  @Autowired
  private FaceCommonService commonService;

  @Autowired
  private FaceSetService faceSetService;
  
  @Autowired
  private FaceService faceService;

  @Autowired
  private StatsService statsService;
  
  @Autowired
  private FdfsService fdfsService;

  @Autowired
  private ActiveCodeUtil activeCodeUtil;

  @Autowired
  private BusPublisher busPublisher;
  
//  @Autowired
//  private Cache downloadTokenCache;

  @Autowired
  private StringRedisTemplate stringRedisTemplate;
  
  @Autowired
  private MqttLogService mqttLogService;
  
  @Autowired
  private CommonSender sender;
  
  @Autowired
  @Qualifier("mqttSenderWorker")
  private ExecutorService mqttSenderWorker;
  
  @Autowired
  private Registration registration;
  
  @Value(value = "${face.image.save}")
  private boolean saveImage;
  
  @Value(value = "${stats.save}")
  private boolean saveStats;
  
  @Value(value = "${mqtt.log.repush}")
  private boolean repush;

  /**
   * 设备上线消息处理
   * @param clientId
   */
  @CmdHandler(code = CmdCode.CONNECT,type = Type.REQUEST)
  public OnlineAck online(@MqttMessageParam(ParamType.clientId) String clientId) {
    OnlineAck onlineAck = new OnlineAck();
    Device existed = deviceService.findDevice(clientId);
    if(existed == null){
      throw new IllegalArgumentException("device["+clientId+"] not exists.");
    }
    deviceService.deviceOnline(clientId, ServiceUtils.getServiceNode(registration));
    
    try {
      busPublisher.publish(new RemoteMessage<DeviceStatus>(CommonConstants.DEVICE_STATUS_MESSAGE_TYPE, new DeviceStatus(clientId, true)), existed.getAppKey());
    } catch (Exception e) {
      log.error("publish device status message exception", e);
    }
    
    //设定为可配置
    if (repush) {
      //异步推送历史失败消息给设备
      mqttSenderWorker.execute(() -> {
        //日志可能堆积很多，导致内存加载很大
        List<MqttLog> logs = mqttLogService.findByDeviceSn(clientId);
        if (!CollectionUtils.isEmpty(logs)) {
          for (MqttLog mqttLog : logs) {
            try {
              sender.repush(mqttLog);
            } catch (Exception e) {
              log.error("repush failed, log:" + mqttLog, e);
            }
          }
        }
      });
    }
    onlineAck.setSuccess(true);
    return onlineAck;
  }

  /**
   * 设备下线消息处理
   * @param clientId
   */
  @CmdHandler(code = CmdCode.DISCONNECT)
  public void offline(@MqttMessageParam(ParamType.clientId) String clientId) {
    Device existed = deviceService.findDevice(clientId);
    if(existed == null){
      throw new IllegalArgumentException("device["+clientId+"] not exists.");
    }
    deviceService.deviceOffline(clientId, "");
    try {
      busPublisher.publish(new RemoteMessage<DeviceStatus>(CommonConstants.DEVICE_STATUS_MESSAGE_TYPE, new DeviceStatus(clientId, false)), existed.getAppKey());
    } catch (Exception e) {
      log.error("publish device status message exception", e);
    }
  }

  /**
   * 设备数据透传给业务应用
   * @param clientId
   * @param message
   * @throws Exception
   */
  @CmdHandler(code = CmdCode.REMOTE_MESSAGE)
  public void remoteMessage(@MqttMessageParam(ParamType.clientId) String clientId, RemoteMessage<String> message) throws Exception {
    Device existed = deviceService.findDevice(clientId);
    if(existed == null){
      throw new IllegalArgumentException("device["+clientId+"] not exists.");
    }
    busPublisher.publish(message, existed.getAppKey());
  }

  /**
   * 设备绑定人脸库
   * @param faceSet
   */
  @CmdHandler(code = CmdCode.FACESET_BIND)
  public void faceSetBind(FaceSetMsg faceSet) {
    Device existed = deviceService.findDevice(faceSet.getDeviceSn());
    if(existed == null){
      throw new IllegalArgumentException("device["+faceSet.getDeviceSn()+"] not exists.");
    }
    existed.setFacesetTokens(faceSet.getFacesetTokens());
    deviceService.save(existed);
  }

  /**
   * 设备添加人脸
   * @param msg
   * @throws Exception
   */
  @CmdHandler(code = CmdCode.ADD_FACE)
  public void addFace(FaceMsg msg) throws Exception {
    Device device = deviceService.findDevice(msg.getDeviceSn());
    if(device == null){
      throw new IllegalArgumentException("device["+msg.getDeviceSn()+"] not exists.");
    }
    
    byte[] img = Base64.getDecoder().decode(msg.getFaceImg());
    
    Face face = new Face();
    face.setToken(UUID.randomUUID().toString());
    face.setOuterId(msg.getOuterId());
    face.setFeature(FeatureUtils.decode(msg.getFeature()));
    //保存人脸照片
    if (saveImage) {
      String fileId = fdfsService.uploadToFastDFS(face.getToken() + "_cap.jpg", img);
      face.setImgUrl(fdfsService.getFileUrl(fileId));
      face.setImgId(fileId);
    }
    //保存人脸特征值
    commonService.saveFeature(face);
    //保存人脸基本信息
    commonService.saveFace(face);
    //人脸库添加人脸
    faceSetService.addFace(true, msg.getFacesetToken(), device.getAppKey(), face.getToken());
    
    FaceInfo faceInfo = new FaceInfo();
    faceInfo.setFacesetToken(msg.getFacesetToken());
    faceInfo.setFaceImg(img);
    faceInfo.setFaceToken(face.getToken());
    //通知业务应用设备添加人脸
    busPublisher.publish(new RemoteMessage<FaceInfo>(msg.getMsgType(), faceInfo), device.getAppKey());
  }
  
  /**
   * 设备删除人脸
   * @param msg
   * @throws Exception
   */
  @CmdHandler(code = CmdCode.DELETE_FACE)
  public void deleteFace(FaceDeleteMsg msg) throws Exception {
    Device device = deviceService.findDevice(msg.getDeviceSn());
    if(device == null){
      throw new IllegalArgumentException("device["+msg.getDeviceSn()+"] not exists.");
    }
    
    String[] faceTokens = null;
    if (msg.getFaceTokens() != null && msg.getFaceTokens().length > 0) {
      faceTokens = msg.getFaceTokens();
    } else if (msg.getOuterIds() != null && msg.getOuterIds().length > 0) {//根据设备生成的人脸ID查询出要删除的人脸token
      List<Face> faces = commonService.findByOuterIds(Arrays.stream(msg.getOuterIds()).collect(Collectors.toList()));
      if (CollectionUtils.isEmpty(faces)) {
        return;
      }
      
      List<String> tokens = faces.stream().map(face -> face.getToken()).collect(Collectors.toList());
      faceTokens = tokens.toArray(new String[0]);
    } else {
      return;
    }
    
    faceSetService.removeFace(true, msg.getFacesetToken(), device.getAppKey(), faceTokens);
    //通知业务应用设备删除人脸
    busPublisher.publish(new RemoteMessage<FaceDeleteInfo>(msg.getMsgType(), new FaceDeleteInfo(msg.getFacesetToken(), faceTokens)), device.getAppKey());
  }

  //设备上报识别日志
  @CmdHandler(code = CmdCode.REPORT_STATS)
  public void reportStats(StatsInfo stats) {
    if (saveStats) {
      statsService.createStats(stats);
    }
  }

  /**
   * 设备是否激活
   * @param msg
   * @return
   */
  @CmdHandler(code = CmdCode.DETECT_ACTIVE, type = Type.REQUEST)
  public ActiveMsgAck detectActive(ActiveMsg msg) {
    Device device = deviceService.findDevice(msg.getDeviceSn());
    if(device == null){
      throw new IllegalArgumentException("device["+msg.getDeviceSn()+"] not exists.");
    }
    Boolean activated = device.getActivated();
    return new ActiveMsgAck(activated);
  }

  /**
   * 激活设备
   * @param msg
   * @return
   */
  @CmdHandler(code = CmdCode.ACTIVE, type = Type.REQUEST)
  public ActiveMsgAck active(ActiveMsg msg) {
    String code = msg.getActivateCode();
    String sn = msg.getDeviceSn();
    Device device = deviceService.findDevice(sn);
    if(device == null){
      throw new IllegalArgumentException("device["+msg.getDeviceSn()+"] not exists.");
    }
    boolean match = activeCodeUtil.match(code, device.getModel(), device.getSn());
    if (match) {
      deviceService.activateDevice(sn);
      return new ActiveMsgAck(true);
    } else {
      return new ActiveMsgAck(false);
    }
  }

  /**
   * 获取人脸库
   * @param clientId
   * @return
   */
  @CmdHandler(code = CmdCode.LIST_FACESETS, type = Type.REQUEST)
  public List<FaceSetData> listFaceSets(@MqttMessageParam(ParamType.clientId) String clientId,FaceSearchsMsg searchsMsg) {
    Device device = deviceService.findDevice(clientId);
    if(device == null){
      throw new IllegalArgumentException("device["+clientId+"] not exists.");
    }
    List<FaceSet> facesetList = new ArrayList<>();
    for(String facesetToken : device.getFacesetTokens().split("\\,")){
      try {
        facesetList.add(commonService.findOneFaceset(facesetToken));
      } catch (FacePlatException e) {
        throw new IllegalArgumentException("facesets["+facesetToken+"] not exists.");
      }
    }
    List<FaceSetData> faceSetDatas = new ArrayList<>();
    String extend = searchsMsg.getExtend();
    String[] extendEntity = new String[]{};
    if(!StringUtils.isEmpty(extend)){
      extendEntity = extend.split("\\:");
    }
    if (!CollectionUtils.isEmpty(facesetList)) {
      String[] finalExtendEntity = extendEntity;
      facesetList.stream().forEach((faceset) -> {
        //如果扩展属性不为空，则表示携带扩展属性A:a，来获取人脸库
        if(finalExtendEntity != null && finalExtendEntity.length > 0){
          //
          if(faceset.getExtendMap() != null && faceset.getExtendMap().size() > 0){
            Map<String,String> extendMap = faceset.getExtendMap();
            if(extendMap.containsKey(finalExtendEntity[0]) && extendMap.get(finalExtendEntity[0]).equals(finalExtendEntity[1])){
              FaceSetData data = new FaceSetData();
              data.setFacesetToken(faceset.getFacesetToken());
              data.setDisplayName(faceset.getDisplayName());
              data.setCapacity(faceset.getCapacity());
              data.setPriority(faceset.getPriority());
              data.setThreshold(faceset.getThreshold());
              faceSetDatas.add(data);
            }
          }
        }else {
          FaceSetData data = new FaceSetData();
          data.setFacesetToken(faceset.getFacesetToken());
          data.setDisplayName(faceset.getDisplayName());
          data.setCapacity(faceset.getCapacity());
          data.setPriority(faceset.getPriority());
          data.setThreshold(faceset.getThreshold());
          faceSetDatas.add(data);
        }
      });
    }
    return faceSetDatas;
  }
  
  /**
   * 获取人脸库特征值下载令牌
   * @param req
   * @param clientId
   * @return
   * @throws FacePlatException
   */
  @CmdHandler(code = CmdCode.FACESET_DOWNLOAD, type = Type.REQUEST)
  public DownloadMsg download(DownloadReq req, @MqttMessageParam(ParamType.clientId) String clientId) throws FacePlatException {
    String facesetToken = req.getFacesetToken();
    if(faceSetService.exists(facesetToken)){
      //TODO 判断设备是否有访问该faceset的权限
      String downloadKey = UUID.randomUUID().toString();
      stringRedisTemplate.opsForValue().set(downloadKey,facesetToken);
      //downloadTokenCache.put(new Element(downloadKey, facesetToken));
      DownloadMsg msg = new DownloadMsg();
      msg.setDownloadToken(downloadKey);
      msg.setFacesetToken(facesetToken);
      return msg;
    }else{
      throw new FacePlatException(ErrorType.FACESET_NOT_EXIST);
    }
  }
  
  /**
   * 人脸检索
   * @param regMsg
   * @return
   * @throws FacePlatException
   */
  @CmdHandler(code = CmdCode.FACE_SEARCH, type = Type.REQUEST)
  public List<Result> faceRec(FaceRecMsg regMsg) throws FacePlatException {
    SearchParam param = new SearchParam();
    param.setFacesetToken(regMsg.getFacesetToken());
    param.setFaceFeature(regMsg.getFeature());
    param.setResultCount(regMsg.getResultCount());
    SearchResult result = faceService.search(param);
    
    return result.getResults();
  }

  /**
   * Description: 存在<br>
   *
   * @author guming <br>
   * @taskId
   * @param searchsMsg <br>
   * @return <br>
   */
  @CmdHandler(code = CmdCode.FACE_SEARCH_ONE ,type = Type.REQUEST)
  public SearchOneResult faceSearchOne(@MqttMessageParam(ParamType.clientId) String clientId, FaceSearchsMsg searchsMsg) throws FacePlatException{

    Device device = deviceService.findDevice(clientId);
    if(device == null){
      throw new IllegalArgumentException("device["+clientId+"] not exists.");
    }
    List<FaceSet> faceSets = new ArrayList<>();
    SearchsParam param = new SearchsParam();

    String extend = searchsMsg.getExtend();
    String[] extendEntity = new String[]{};
    if(!StringUtils.isEmpty(extend)){
      extendEntity = extend.split("\\:");
    }

    //升序排列，优先级数值越小越靠前
    List<DeviceFaceSet> list = deviceService.findFaceSetBySn(device.getSn());
    if(!CollectionUtils.isEmpty(list)){
      for(DeviceFaceSet deviceFaceSet : list){

        FaceSet faceSet = commonService.findOneFaceset(deviceFaceSet.getFaceSetToken());
        if(faceSet == null){
          continue;
        }
        faceSet.setPriority(deviceFaceSet.getPriority());
        if(extendEntity != null && extendEntity.length > 0){
          if(faceSet.getExtendMap() != null && faceSet.getExtendMap().size() > 0){
            Map<String,String> extendMap = faceSet.getExtendMap();
            if(extendMap.containsKey(extendEntity[0]) && extendMap.get(extendEntity[0]).equals(extendEntity[1])){
              faceSets.add(faceSet);
            }
          }
        }else {
          faceSets.add(faceSet);
        }
      }
    }
    param.setFaceSets(faceSets);
    param.setFaceFeature(FeatureUtils.decode(searchsMsg.getFeature()));
    param.setResultCount(searchsMsg.getResultCount());
    param.setFeature(FeatureUtils.MD5(searchsMsg.getFeature()));
    //人脸探测,只判断是否
    SearchOneResult result = faceService.searchOne(param);

    if(searchsMsg.isNeedPush2Cloud()){
      try {
        busPublisher.publish(new RemoteMessage<SearchOneResult>(searchsMsg.getMsgType(),result), device.getAppKey());
      }catch (Exception e){
        log.error("faceSearchs failed, log:", e);
      }
    }
    result.setFaceID(searchsMsg.getFaceID());
    result.setSessionID(searchsMsg.getSessionID());
    return result;
  }
  
  @CmdHandler(code = 3)
  public void testMsgPack(SocietyMessage msg) {
    System.out.println("CommonHandler:" + msg);
    if (msg != null) {
      try {
        FileUtils.writeByteArrayToFile(new File("F:\\sample.png"), msg.getData().getPersons().get(0).getImage());
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
  
  @CmdHandler(code = 4)
  public void testReport(Status status, BinaryStatus bStatus) {
    //System.err.println(status);
    System.err.println(bStatus);
    if (bStatus != null) {
      try {
        FileUtils.writeByteArrayToFile(new File("F:\\sample.png"), bStatus.getImgBytes());
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
  
  @CmdHandler(code = 5)
  public void testProtobuf(AddressBookMessage msg) {
    System.out.println("CommonHandler:" + msg.getData());
    try {
      FileUtils.writeByteArrayToFile(new File("F:\\sample.png"), msg.getData().getPeopleList().get(0).getImg().toByteArray());
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  
}
